﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Mail;
using System.Web.Security;
using Argotic.Syndication.Specialized;
using Dexter.Async.Services;
using Dexter.DataTransferObject;
using Dexter.Domain.Model;
using Dexter.Logger.Contracts;
using Dexter.Services.Helpers;
using Topics.Radical.Validation;

namespace Dexter.Services.Implementation {
	/// <summary>
	/// 	todo: check UserEmail in comments (it seems null) this class is specific for wordpress, it makes assumptions to work toghether with our exporter
	/// </summary>
	public class ImportServiceWordpress : IImportService {
		public static string TagCategoryDescriptionQualifier = "__tag__";
		readonly IAsyncService asyncService;
		readonly ICategoryService categegoryDataService;
		readonly ICommentService commentDataService;
		readonly ILogger logger;
		readonly IPostService postDataService;
		readonly List<string> tags = new List<string>();

		public ImportServiceWordpress(ILogger logger,
		                              ICategoryService categegoryDataService,
		                              IPostService postDataService,
		                              ICommentService commentDataService, IAsyncService asyncService) {
			this.logger = logger;
			this.categegoryDataService = categegoryDataService;
			this.postDataService = postDataService;
			this.commentDataService = commentDataService;
			this.asyncService = asyncService;
			this.logger.Debug("Creating new ImportService");
		}

		#region IImportService Members

		public IAsyncResult ImportFromBlogMlAsync(byte[] file) {
			Ensure.That(file).IsNotNull();

			return asyncService.AsyncCall(o => ImportFromBlogMl(o), file);
		}

		public ImportResultDto ImportFromBlogMl(object obj) {
			Ensure.That(obj).IsNotNull();

			logger.Warn("BlogML Wordpress importing routine start.");

			ImportResultDto result = new ImportResultDto();

			try {
				byte[] file = (byte[]) obj;

				BlogMLDocument document;
				using (Stream s = new MemoryStream(file)) {
					document = new BlogMLDocument();
					document.Load(s);
				}


				#region Add Users

				Dictionary<string, string> users = ImportUsers(document);

				#endregion

				#region Add Categories

				Dictionary<string, Category> categories = ImportCategories(document);

				#endregion

				Category defaultCategory = categegoryDataService.GetDefaultCategoryWithChilds();

				IEnumerable<BlogMLPost> blogMlPosts = document.Posts;

				// process posts to remove eventually duplicated items
				IEnumerable<BlogMLPost> processedPosts = PreprocessBlogMlPostToRemoveDuplicates(blogMlPosts);

				foreach (BlogMLPost post in processedPosts) {
					if (post.ApprovalStatus == BlogMLApprovalStatus.Approved) {
						string postTitle = BlogMlImportServiceHelpers.DecodeBlogMlTextConstructutContent(post.Title).DecodeHtml();

						logger.WarnFormat("Processing post: {0}", postTitle);

						#region Add Posts

						logger.Debug("Retrieving category");

						// extract the tags encoded in the category section
						List<string> tagsList = new List<string>();
						
						foreach (string tag in post.Categories) {
							if (tag.StartsWith(TagCategoryDescriptionQualifier))
								tagsList.Add(tag.Replace(TagCategoryDescriptionQualifier, string.Empty).ToLower());
						}

						string body = BlogMlImportServiceHelpers.DecodeBlogMlTextConstructutContent(post.Content);

						logger.Debug("Retriving the WLW tags from the body.");
						string[] extractedTags = PostHelpers.RetrieveTagsFromBody(body);

						// merge the 2 tag list
						foreach (string tag in extractedTags) {
							if (!tagsList.Contains(tag.ToLower()))
								tagsList.Add(tag.ToLower());
						}
						// add tags encoded as categories

						// Logger.Debug("Clean body from WLW tags.");
						// questionable, if we do this we cannot reopen and exit the post with WLW again
						// string body = Utility.RemovePlumbingFromWlwHtmlTags(body);

						body = PostHelpers.PreProcessUnformattedMessages(body);

						// wordpress saved the slug in the post_name field
						string slug = BlogMlImportServiceHelpers.DecodeBlogMlTextConstructutContent(post.Name).DecodeHtml();

						//allow updating already posted blogs
						Post pst = null;
						if (false == string.IsNullOrEmpty(slug))
							pst = postDataService.GetBySlug(slug);

						if (pst != null) {
							pst.FormattedBody = body;
							pst.Username = users[post.Authors[0]];
						}
						else
							pst = Post.CreateNewPost(
								postTitle,
								null,
								body,
								users[post.Authors[0]]
								);

						pst.PublishDate = post.CreatedOn;
						pst.BreakOnAggregate = body.Contains("[more]");
						pst.EnableComment();
						pst.Publish();
						
						string[] tags = tagsList.ToArray();
						
						if (tags.Any()) {
							tags.Where(x => !string.IsNullOrEmpty(x)).ForEach(x => pst.Tags.Add(TagItem.CreateNewTagItem(x, pst)));
						}

						pst.Slug = slug;

						// process multiple categories
						foreach (string category in post.Categories) {
							if (!category.StartsWith(TagCategoryDescriptionQualifier) && categories.ContainsKey(category)) {
								Category cdto = categories[category];
								if (!pst.Categories.Contains(cdto))
									pst.AddCategory(cdto);
							}
						}
						// add a default categ if needed
						if (pst.Categories.Count == 0)
							pst.AddCategory(defaultCategory);

						logger.WarnFormat("Saving Post: {0} - Url: {1}", postTitle, post.Url != null ? post.Url.ToString() : string.Empty);

						postDataService.Save(pst);

						#endregion

						#region Add Comments

						foreach (BlogMLComment comment in post.Comments) {
							logger.WarnFormat("Creating Comment for the post {0}.", pst.Id);
							Uri userWebsite = comment.UserUrl;
							MailAddress userEmail = BlogMlImportServiceHelpers.GetMailAddressSafe(comment.UserEmailAddress, logger);
							string userName = string.IsNullOrEmpty(comment.UserName)
							                  	? "anonymous"
							                  	: comment.UserName;
							Comment cmt = Comment.CreateNewComment(
								pst,
								userWebsite,
								userEmail,
								BlogMlImportServiceHelpers.DecodeBlogMlTextConstructutContent(comment.Content),
								userName,
								null
								);
							cmt.Approved = comment.ApprovalStatus == BlogMLApprovalStatus.Approved;
							cmt.CreationDate = comment.CreatedOn;
							cmt.Notify = false;
							logger.WarnFormat("Saving Comment for the post {0}.", pst.Id);
							logger.WarnFormat("Comment: {0}", cmt.ToString());

							commentDataService.Save(cmt);
						}

						#endregion
					}
				}

				// calling the code to recount every post in each category should not be needed
				//_categegoryDataService.UpdatePostsContentNumber();

				logger.Warn("Import Complete.");

				result.Succeded = true;
			}
			catch (Exception e) {
				logger.Error("Unhandled exception was throw in the ImportService", e);

				result.ErrorMessage = e.Message;
				result.Succeded = false;
			}
			finally {
				logger.Warn("BlogML Wordpress importing routine end.");
			}

			return result;
		}

		#endregion

		Dictionary<string, Category> ImportCategories(BlogMLDocument document) {
			Dictionary<string, Category> categories = new Dictionary<string, Category>();

			foreach (BlogMLCategory category in document.Categories) {
				string categoryTitle = BlogMlImportServiceHelpers.DecodeBlogMlTextConstructutContent(category.Title);

				if (category.Description != TagCategoryDescriptionQualifier) {
					Category cat = categegoryDataService.GetCategoryByNameWithChilds(categoryTitle);

					if (cat == null) {
						// we need to insert the category, let's look if it has a parent
						Category parentCategory = null;

						if (!string.IsNullOrEmpty(category.ParentId))
							// get the parent category
							foreach (BlogMLCategory mlCategory in document.Categories)
								if (mlCategory.Id == category.ParentId) {
									parentCategory = categegoryDataService.GetCategoryByNameWithChilds(BlogMlImportServiceHelpers.DecodeBlogMlTextConstructutContent(mlCategory.Title));
									break;
								}

						logger.WarnFormat("Creating category {0}.", categoryTitle);
						cat = Category.CreateNewCategory(categoryTitle, parentCategory);
						categegoryDataService.Save(cat);
						logger.WarnFormat("Category {0} created.", categoryTitle);
					}

					if (!categories.ContainsKey(category.Id))
						categories.Add(category.Id, categegoryDataService.GetCategoryByNameWithChilds(categoryTitle));
				}
				else {
					// this is a post tag
					tags.Add(categoryTitle);
				}
			}
			return categories;
		}

		Dictionary<string, string> ImportUsers(BlogMLDocument document) {
			Dictionary<string, string> users = new Dictionary<string, string>();

			foreach (BlogMLAuthor author in document.Authors) {
				string authorTitle = BlogMlImportServiceHelpers.DecodeBlogMlTextConstructutContent(author.Title);
				if (Membership.GetUser(authorTitle) == null) {
					logger.WarnFormat("Creating author {0}.", authorTitle);
					Membership.CreateUser(authorTitle, "d3xt3r", author.EmailAddress);

					logger.WarnFormat("Adding author {0} to role Poster.", authorTitle);
					Roles.AddUsersToRole(new[]
					                     	{
					                     		authorTitle
					                     	}, "Poster");

					logger.WarnFormat("Author {0} ready.", authorTitle);
				}

				if (!users.ContainsKey(author.Id))
					users.Add(author.Id, authorTitle);
			}

			return users;
		}

		static IEnumerable<BlogMLPost> PreprocessBlogMlPostToRemoveDuplicates(IEnumerable<BlogMLPost> blogMlPosts) {
			IList<BlogMLPost> processedPosts = new List<BlogMLPost>();
			foreach (BlogMLPost post in blogMlPosts) {
				// a post is considered valid if:
				// 1- the title is not present in the processed posts list
				// 2- a post with the same title but with the URL containing a slug replaces an eventual identical post made whose url contains a post id
				BlogMLTextConstruct blogMlTextConstruct = post.Title;
				BlogMLPost presentPost = processedPosts.FirstOrDefault(p => p.Title == blogMlTextConstruct);

				if (presentPost == null)
					processedPosts.Add(post);
				else {
					// check for a possible slug and replace the post if needed
					if ((post.Url != null) && IsSlug(post.Url)) {
						processedPosts.Remove(presentPost);
						processedPosts.Add(post);
					}
				}
			}
			return processedPosts;
		}

		static bool IsSlug(Uri url) {
			string lastUrl = url.Segments.Last();
			return lastUrl.Replace(".aspx", "").IsNumber() == false;
		}
	}
}